package com.ruoyi.utils;

import java.math.BigDecimal;

public class MapUtils {
    private static double x_pi = Math.PI * 3000.0 / 180.0;
    private static double a = 6378245.0;
    private static double ee = 0.00669342162296594323;

    /**
     * 百度坐标系 (BD-09) 转火星坐标系 (GCJ-02)
     *
     * @param lng 百度经度
     * @param lat 百度纬度
     * @return 火星坐标数组
     */
    public static double[] bd09ToGcj02(double lng, double lat) {
        double x = lng - 0.0065;
        double y = lat - 0.006;
        double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * x_pi);
        double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * x_pi);
        double gcjLng = z * Math.cos(theta);
        double gcjLat = z * Math.sin(theta);
        return new double[]{gcjLng, gcjLat};
    }

    /**
     * 火星坐标系 (GCJ-02) 转高德坐标系 (GCJ-02)
     *
     * @param lng 火星经度
     * @param lat 火星纬度
     * @return 高德坐标数组
     */
    public static double[] gcj02ToGps84(double lng, double lat) {
        double[] gps = transform(lng, lat);
        double longitude = lng * 2 - gps[0];
        double latitude = lat * 2 - gps[1];
        return new double[]{longitude, latitude};
    }

    /**
     * 经纬度坐标转换方法
     *
     * @param lng 经度
     * @param lat 纬度
     * @return 坐标数组
     */
    private static double[] transform(double lng, double lat) {
        if (outOfChina(lng, lat)) {
            return new double[]{lng, lat};
        }
        double dLat = transformLat(lng - 105.0, lat - 35.0);
        double dLng = transformLng(lng - 105.0, lat - 35.0);
        double radLat = lat / 180.0 * Math.PI;
        double magic = Math.sin(radLat);
        magic = 1 - ee * magic * magic;
        double sqrtMagic = Math.sqrt(magic);
        dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * Math.PI);
        dLng = (dLng * 180.0) / (a / sqrtMagic * Math.cos(radLat) * Math.PI);
        double mgLat = lat + dLat;
        double mgLng = lng + dLng;
        return new double[]{mgLng, mgLat};
    }

    /**
     * 判断是否在国内，不在国内则不做偏移
     *
     * @param lng 经度
     * @param lat 纬度
     * @return 是否在国内
     */
    private static boolean outOfChina(double lng, double lat) {
        return lng < 72.004 || lng > 137.8347 || lat < 0.8293 || lat > 55.8271;
    }

    /**
     * 纬度转换
     *
     * @param x 经度
     * @param y 纬度
     * @return 转换后的纬度
     */
    private static double transformLat(double x, double y) {
        double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
        ret += (20.0 * Math.sin(6.0 * x * Math.PI) + 20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0 / 3.0;
        ret += (20.0 * Math.sin(y * Math.PI) + 40.0 * Math.sin(y / 3.0 * Math.PI)) * 2.0 / 3.0;
        ret += (160.0 * Math.sin(y / 12.0 * Math.PI) + 320 * Math.sin(y * Math.PI / 30.0)) * 2.0 / 3.0;
        return ret;
    }

    /**
     * 经度转换
     *
     * @param x 经度
     * @param y 纬度
     * @return 转换后的经度
     */
    private static double transformLng(double x, double y) {
        double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
        ret += (20.0 * Math.sin(6.0 * x * Math.PI) + 20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0 / 3.0;
        ret += (20.0 * Math.sin(x * Math.PI) + 40.0 * Math.sin(x / 3.0 * Math.PI)) * 2.0 / 3.0;
        ret += (150.0 * Math.sin(x / 12.0 * Math.PI) + 300.0 * Math.sin(x / 30.0 * Math.PI)) * 2.0 / 3.0;
        return ret;
    }

    /**
     * 高德坐标系 (GCJ-02) 转百度坐标系 (BD-09)
     *
     * @param lng 火星经度
     * @param lat 火星纬度
     * @return 百度坐标数组
     */
    public static double[] gcj02ToBd09(double lng, double lat) {
        double z = Math.sqrt(lng * lng + lat * lat) + 0.00002 * Math.sin(lat * x_pi);
        double theta = Math.atan2(lat, lng) + 0.000003 * Math.cos(lng * x_pi);
        double bdLng = z * Math.cos(theta) + 0.0065;
        double bdLat = z * Math.sin(theta) + 0.006;
        return new double[]{bdLng, bdLat};
    }

    /**
     * BD-09 坐标转换成 WGS84 坐标
     *
     * @param bdLng 百度经度
     * @param bdLat 百度纬度
     * @return WGS84坐标数组
     */
    public static double[] bd09ToGps84(double bdLng, double bdLat) {
        double[] gcj02 = bd09ToGcj02(bdLng, bdLat);
        return gcj02ToGps84(gcj02[0], gcj02[1]);
    }

    /**
     * WGS84坐标转BD-09坐标
     *
     * @param lng WGS84经度
     * @param lat WGS84纬度
     * @return BD-09坐标数组
     */
    public static double[] gps84ToBd09(double lng, double lat) {
        double[] gcj02 = gps84ToGcj02(lng, lat);
        return gcj02ToBd09(gcj02[0], gcj02[1]);
    }

    /**
     * WGS84坐标转换成火星坐标系(GCJ-02)
     *
     * @param lng WGS84经度
     * @param lat WGS84纬度
     * @return 火星坐标数组
     */
    public static double[] gps84ToGcj02(double lng, double lat) {
        if (outOfChina(lng, lat)) {
            return new double[]{lng, lat};
        }
        double dLat = transformLat(lng - 105.0, lat - 35.0);
        double dLng = transformLng(lng - 105.0, lat - 35.0);
        double radLat = lat / 180.0 * Math.PI;
        double magic = Math.sin(radLat);
        magic = 1 - ee * magic * magic;
        double sqrtMagic = Math.sqrt(magic);
        dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * Math.PI);
        dLng = (dLng * 180.0) / (a / sqrtMagic * Math.cos(radLat) * Math.PI);
        double mgLat = lat + dLat;
        double mgLng = lng + dLng;
        return new double[]{mgLng, mgLat};
    }

    /**
     * 计算两点之间距离
     *
     * @param p1 第一个坐标点
     * @param p2 第二个坐标点
     * @return 两点间距离(千米)
     */
    public static double calculateDistance(double[] p1, double[] p2) {
        double lat1 = p1[1];
        double lng1 = p1[0];
        double lat2 = p2[1];
        double lng2 = p2[0];
        double radLat1 = Math.toRadians(lat1);
        double radLat2 = Math.toRadians(lat2);
        double deltaLng = Math.toRadians(lng1 - lng2);

        double distance = Math.round(
                Math.acos(Math.sin(radLat1) * Math.sin(radLat2)
                        + Math.cos(radLat1) * Math.cos(radLat2)
                        * Math.cos(deltaLng))
                        * a * 10000d / 1000d) / 10000d;

        return BigDecimal.valueOf(distance).setScale(4, BigDecimal.ROUND_HALF_UP).doubleValue();
    }
}
